////////////////////////////////////////////////////////////////////////////////
/// Project: GTest extension (GTestEX)
///
/// \file gtestex.h
///
/// \brief The purpose of this class is to add cross-os support for
/// for stream data
///
/// \version 0.1.0
///
/// \date   May 27, 2011
///
/// \author contact@afonseca.org (Armando Fonseca)
///
/// \attention TCPClient and GTestListener are based on the StreamListener
///  classes from the Google Test framework.
////////////////////////////////////////////////////////////////////////////////

#ifndef GTESTEX_H_
#define GTESTEX_H_

//------------------------------------------------------------------------------
// C Library
#include <cassert>
#include <cstring>

//------------------------------------------------------------------------------
// C++ Library
#include <iosfwd>
#include <iostream>

//------------------------------------------------------------------------------
// 3rd party library
#include "gtest/gtest.h"

// Cross os Library
#if GTEST_OS_WINDOWS_MINGW || GTEST_OS_WINDOWS
# include <winsock2.h>
# include <ws2tcpip.h>
//  # define GTEST_CAN_STREAM_RESULTS_ 1
#endif

/// \namespace gtestex Encapsulate components for extending GTest.
namespace gtestex {

using namespace ::testing;
using namespace ::testing::internal;

/// \namespace msg_transmit functionalities to establish communication between
///  gtest and gtestex.
namespace msg_transmit {

/// \class TCPClient gtestex.h "gtestex."
/// represents the client side of TCP connection. By
/// passing the hostname and port, TCPClient object can open/close and send
/// records to the tcp server. Receiving records from the server has not been
/// implemented because GTestEX doesn't need that features at this moment.
class TCPClient {
 public:

  //----------------------------------------------------------------------------
  // Constants 

  /// Invalid value for a socket descriptor
  static const int kInvalidSockFd = -1;

  //----------------------------------------------------------------------------
  // Constructors & Destructor

  /// An argument constructor that initialize member variables. It doesn't
  /// initialize a TCP connection.
  /// \param host [in] the hostname of a server. ie : www.example.com
  /// \param port [in] the port within the hostname server. ie: 3490
  TCPClient(const string& host, const string& port)
      : host_name_(host), port_num_(port), sockfd_(kInvalidSockFd) {
    
    // Note: Google doesn't use exception on C++ code
    assert(!host_name().empty());
    assert(!host_name().empty());
    assert(kInvalidSockFd == sockfd());
  }

  /// Destruct the TCP object.
  /// If there is a TCP connection open, it closes it.
  virtual ~TCPClient() {
    if (IsConnected()) { CloseConnection(); }
  }

  //----------------------------------------------------------------------------
  // Public interface

  /// Close a TCP connection for port and host if open or do nothing if close.
  /// \return zero if connection is close or a nonzero for error code. Error
  /// code are platform dependent.
  int CloseConnection() { return OSCloseConnection(); }

  /// Check whether there is a connection for port and host.
  /// \return true if the tcp server is ready for sending data, false otherwise.
  bool IsConnected() { return (kInvalidSockFd != sockfd()); }

  /// Open tcp client connection for the giving host and port.
  /// \return zero if connection is open or a nonzero for error code. Error
  /// code are platform dependent.
  int OpenConnection() { 
    return OSOpenConnetion(); 
  }

  /// Send an string record to the giving host/port.
  /// If Add_new_line is  true (by default), SendMessage will append a new line
  /// at the end of the string record signaling that the record packet
  /// is ready to be send to the server. If add_new_line is false, it will
  /// enqueue records and create a bigger packet until a new line is is giving.
  /// If a new line is in the record string, the record packet will be ready
  /// to be send regardless the value of add_new_line.
  /// \param record [in] string record to be send to the server, ie: Hello
  /// \param add_new_line [in] boolean (true by default) to append a new line to
  /// the string record.
  /// \return true if the tcp server is ready for sending data, false otherwise.
  int SendMessage(const string& record, bool add_new_line = true) {
    return OSSendMessage(record, add_new_line);
  }

 private:

  //----------------------------------------------------------------------------
  // Getters/setters

  const string& host_name() const { return host_name_; }

  const string& port_num() const { return port_num_; }

  int sockfd() const { return sockfd_; }
  int sockfd() { return sockfd_; }
  void set_sockfd(const int sockfd) { sockfd_ = sockfd; }

  //----------------------------------------------------------------------------
  // Operating System dependent member functions

  // Close connection (Operating System dependent)
  virtual int OSCloseConnection();

  // Open a TCP connection (Operating System dependent)
  virtual int OSOpenConnetion();

  // Send records to a TCP server (Operating System dependent)
  virtual int OSSendMessage(const string& record, bool add_new_line);

  const string host_name_;  // ie: 127.0.0.1
  const string port_num_;  // ie: 1234
  // TODO thread safer consideration in the future
  int sockfd_;  // socket file descriptor

  GTEST_DISALLOW_COPY_AND_ASSIGN_(TCPClient);
};


namespace record_utility {

/// Returns field delimeter for gtestex records. The intent of Delimeter() is to
/// define the delimeter value in one place.
inline string::value_type Delimeter() { 
  return ','; 
}

/// Checks if str contains '=', '&', '%', '\n' or ',' characters. If yes,
/// replaces them by "%xx" where xx is their hexadecimal value. For
/// example, replaces "=" with "%3D".  This algorithm is O(strlen(str))
/// in both time and space -- important as the input str may contain an
/// arbitrarily long test failure record and stack trace.
inline string UrlEncode(const char* str) {
  string result("");
  if (NULL == str || '\0' == *str) { return result; }

  for (char ch = *str; '\0' != ch; ch = *++str) {
    if ('%' == ch || '=' == ch ||'&' == ch || '\n' == ch || ',' == ch) {
      result.append(String::Format("%%%02x", static_cast<unsigned char>(ch)));
    } else {
      result.push_back(ch);
    }
  }
  return result;
}


enum RecordId {
  kOnInvalidMemberFunction     = 0,
  kOnTestProgramStart          = 1,
  kOnTestIterationStart        = 2,
  kOnEnvironmentsSetUpStart    = 3,
  kOnEnvironmentsSetUpEnd      = 4,
  kOnTestCaseStart             = 5,
  kOnTestStart                 = 6,
  kOnTestPartResult            = 7,
  kOnTestEnd                   = 8,
  kOnTestCaseEnd               = 9,
  kOnEnvironmentsTearDownStart = 10,
  kOnEnvironmentsTearDownEnd   = 11,
  kOnTestIterationEnd          = 12,
  kOnTestProgramEnd            = 13

};

inline RecordId GetRecordId(const string& record) {
  if (record.empty() || !('1' <= record[0] && '9' >= record[0])) { 
    return kOnInvalidMemberFunction; 
  }
  int r_id = kOnInvalidMemberFunction;
  std::istringstream(record) >> r_id;

  return (r_id >= kOnTestProgramStart && r_id<= kOnTestProgramEnd)
          ? static_cast<RecordId>(r_id) : kOnInvalidMemberFunction;  
}
 
bool IsRecordValid(const string& record); 

void MakeUnitTestRecord(const RecordId mf_id, const UnitTest& unit_test,
                        const int iteration, std::ostream& record_stream);
  
void MakeTestCaseRecord(const RecordId mf_id, const TestCase& test_case,
                        std::ostream& record_stream);
  
void MakeTestInfoRecord(const RecordId mf_id, const TestInfo& test_info,
                        std::ostream& record_stream);

void MakeTestPartResultRecord(const record_utility::RecordId mf_id, 
    const TestPartResult& test_part_result, std::ostream& record_stream);

}
/// Streams test results to the given port on the given host machine.
/// There are four types of test results:
/// -| UnitTest, TestCase, TestInfo, TestPartResult |-
/// Test results are sent as records.
/// First field indicates the record data type and the member function. 
/// First field values ranges from 1 to 13. 
///  UnitTest record values       = 1, 2, 3, 4, 5, 10, 11, 12, 13
///  TestCase record value        = 5, 9
///  TestInfo record values       = 6, 8
/// TestPartResult record values = 7
 
class TCPSendRecordListener : public TestEventListener {
 public:

  //----------------------------------------------------------------------------
  // Constructors & Destructor

  explicit TCPSendRecordListener(TCPClient* tc_ptr) : tcp_client_ptr_(tc_ptr) {
    tcp_client_ptr()->OpenConnection();
    assert(tcp_client_ptr()->IsConnected() == 0); 
  }

  virtual ~TCPSendRecordListener() {
    if (tcp_client_ptr() != NULL) {
      if (tcp_client_ptr()->IsConnected()) {
        tcp_client_ptr()->CloseConnection(); 
      }
      delete tcp_client_ptr();
      set_tcp_client_ptr(NULL);
    }
  }

  //----------------------------------------------------------------------------
  // Public interface

  virtual void OnTestProgramStart(const UnitTest& unit_test) {
    std::ostringstream osstream;   
    record_utility::MakeUnitTestRecord(record_utility::kOnTestProgramStart, 
                                      unit_test, 0, osstream);
    send(osstream.str());

  }

  virtual void OnTestIterationStart(const UnitTest& unit_test , int iteration) {
    std::ostringstream osstream;
    record_utility::MakeUnitTestRecord(record_utility::kOnTestIterationStart,
                                       unit_test, iteration, osstream);
    send(osstream.str());
  }

  virtual void OnEnvironmentsSetUpStart(const UnitTest& unit_test)  {
    std::ostringstream osstream; 
    record_utility::MakeUnitTestRecord(record_utility::kOnEnvironmentsSetUpStart, 
                                      unit_test, 0, osstream);
    send(osstream.str());
  }

  virtual void OnEnvironmentsSetUpEnd(const UnitTest& unit_test) {
    std::ostringstream osstream;
    record_utility::MakeUnitTestRecord(record_utility::kOnEnvironmentsSetUpEnd,
                                      unit_test, 0, osstream);
    send(osstream.str());
  }

  virtual void OnTestCaseStart(const TestCase& test_case) {
    std::ostringstream osstream;
    record_utility::MakeTestCaseRecord(record_utility::kOnTestCaseStart, 
                                      test_case, osstream);
    send(osstream.str()); 
  }

  virtual void OnTestStart(const TestInfo& test_info) {
    std::ostringstream osstream;
    record_utility::MakeTestInfoRecord(record_utility::kOnTestStart, test_info,
                                      osstream);
    send(osstream.str()); 
  }

  virtual void OnTestPartResult(const TestPartResult& test_part_result) {
    std::ostringstream osstream;
    record_utility::MakeTestPartResultRecord(record_utility::kOnTestPartResult,
                                            test_part_result, osstream);
    send(osstream.str());
  }

  virtual void OnTestEnd(const TestInfo& test_info) {
    std::ostringstream osstream;
    record_utility::MakeTestInfoRecord(record_utility::kOnTestEnd, test_info, 
                                      osstream);
    send(osstream.str()); 
  }

  virtual void OnTestCaseEnd(const TestCase& test_case) {
    std::ostringstream osstream;
    record_utility::MakeTestCaseRecord(record_utility::kOnTestCaseEnd, test_case,
                                      osstream);
    send(osstream.str());
  }

  virtual void OnEnvironmentsTearDownStart(const UnitTest& unit_test) {
    std::ostringstream osstream;
    record_utility::MakeUnitTestRecord(record_utility::kOnEnvironmentsTearDownStart,
                                       unit_test, 0, osstream);
    send(osstream.str()); 
  }

  virtual void OnEnvironmentsTearDownEnd(const UnitTest& unit_test) {
    std::ostringstream osstream;
    record_utility::MakeUnitTestRecord(record_utility::kOnEnvironmentsTearDownEnd,
                                      unit_test, 0, osstream);
    send(osstream.str()); 
  }

  virtual void OnTestIterationEnd(const UnitTest& unit_test, int iteration) {
    std::ostringstream osstream;
    record_utility::MakeUnitTestRecord(record_utility::kOnTestIterationEnd, 
                                      unit_test, iteration, osstream);
    send(osstream.str());
  }

  virtual void OnTestProgramEnd(const UnitTest& unit_test) {
    std::ostringstream osstream;
    record_utility::MakeUnitTestRecord(record_utility::kOnTestProgramEnd,
                                      unit_test, 0, osstream);
    tcp_client_ptr()->SendMessage(osstream.str());
    send(osstream.str()); 
  }

 private:

   void send(const string& record);
  //----------------------------------------------------------------------------
  // Getters/setters

  void set_tcp_client_ptr(TCPClient* tc_ptr) { tcp_client_ptr_ = tc_ptr; }
  TCPClient* tcp_client_ptr() { 
    assert(tcp_client_ptr_ != NULL);
    return tcp_client_ptr_; 
  }

  //----------------------------------------------------------------------------
  // Data Members

  TCPClient* tcp_client_ptr_;

  GTEST_DISALLOW_COPY_AND_ASSIGN_(TCPSendRecordListener);
};  // class TCPSendRecordListener

}  /// End of namespace: (msg_transmit)
}  /// End of namespace: (gtestex)

#endif /* GTESTEX_H_ */
